feat(types,cli,verify)!: 只解析 host app 声明过的包 —— NODE_PATH 不再算数,ADR-0093 D5 的墙与启动方式脱钩 (#4719) - #4999
Merged
Conversation
…'s declaration, not on NODE_PATH reachability (#4719) `createHostRequire` returns a CJS `createRequire`, and CJS resolution honours `NODE_PATH` (`Module.globalPaths`). Every pnpm bin shim exports NODE_PATH at the hoisted workspace store before it execs, so any package transitively reachable from anywhere in the workspace resolved "from the host app" — regardless of what that app declared. ADR-0093 D5's wall therefore fired or not according to HOW the process was launched, and its own message told operators to "declare it in the app's package.json", the one thing the CLI never checked. The host lookup is now gated on the host's declaration: a name is looked up in the host's node_modules only when it is a key of `dependencies`, `devDependencies`, `optionalDependencies` or `peerDependencies`. Undeclared names fall back to the importing package's own (ESM, NODE_PATH-blind) resolution, so every framework-owned load is unchanged. The two absences are now reported apart, with their opposite remedies: undeclared points at declaring + installing; declared-but-unresolvable points at the install and says the declaration is not the problem. `hostImportFailureKind()` exposes the classification; both errors keep `code: 'MODULE_NOT_FOUND'`. `serve`'s private `hostDeclaresDependency` (#1597) now delegates to the shared owner, so "declared" cannot mean two things in one file. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 4 package(s): 24 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
xuyushun441-sys
marked this pull request as ready for review
August 3, 2026 19:31
xuyushun441-sys
enabled auto-merge
August 3, 2026 19:31
This was referenced Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4719
维护者已拍板方向 2:只有包名出现在 host app 的
package.json声明里,才去 host 的node_modules里解析它。本 PR 实现该方向,并逐一验证三个消费者。契约 vs 实际
@objectstack/types/node的createHostRequire返回一个 CJScreateRequire,而 CJS 解析认NODE_PATH(Module.globalPaths)。pnpm 生成的 bin shim 第一件事就是于是任何被工作区里任意一个包传递依赖到的包都躺在那个 hoisted store 里,
hostRequire.resolve()一律成功 —— 跟 host app 声明了什么毫无关系。ADR-0093 D5 那道墙因此只在不经 shim 的调用里生效,而它的报错文案一直在教 operator「declare it in the app's package.json」,那恰恰是 CLI 从来没检查过的那件事。cloud
apps/objectos-ee(当时未声明@objectstack/organizations)的实测:pnpm start(经 shim)boot 成功、插件表里有Organizations、D5 一声不吭;node node_modules/@objectstack/cli/bin/run.js serve(不经 shim)则✖ FATAL+ exit 1。同一个 app、同一份package.json、同一个 posture,只因进程怎么被拉起来。方向 2 的实现
落点
packages/types/src/node.ts(#4857 迁移后的唯一源)。dependencies/devDependencies/optionalDependencies/peerDependencies的键时,才走 host 解析。「能被解析到」不再算数 —— 那正是让契约失效的偶然。import())。这条保住了框架自有包的加载路径(serve的 plugin-auth / service-i18n、bootStack的各 service plugin)。裸import()是 ESM,ESM 不认NODE_PATH,所以退路不会把洞重新打开 —— tsup 的 cjs 产物里import()也原样保留,已核对dist/node.js。两类失败经新导出的
hostImportFailureKind(err)暴露分类;两者都仍带code: 'MODULE_NOT_FOUND',isModuleNotFoundError的既有判定不变。fail-fast 文案(三个消费者各自分支):
undeclaredpackage.json里声明并安装;并说明为什么 hoisting /NODE_PATH不被接受declared-unresolvablepnpm install、生产 prune 砍掉了它、dist 没构建);别再回去看那份已经写对的package.json边界形态的取舍(理由)
optionalDependencies算。 npm/pnpm 会装、装失败可容忍;「装上了就是声明了」成立。而且真没装上时,declared-unresolvable分支会精确说出来,不会假装 app 没要过它。peerDependencies算。 一个 app 不是任何人的 peer,把企业插件写在这里很少见;但那仍是在自己的 manifest 里有意点名。决定性理由是已存在的消费场景:serve.ts的 AI 版次门(hostDeclaresDependency,Make optional-plugin loading intent-driven: fail-fast on declared-but-missing, drop presence-based auto-enable #1597)自诞生起就读这四个字段,理由与本单逐字相同(「Gating on a declared dep — not mere resolvability — makes this reliable in a workspace/monorepo」)。这里收三个、那里收四个,等于把「declared」在同一个文件里分裂成两种方言(Prime Directive Add comprehensive test suite for Zod schema validation #12)。所以本 PR 让那个私有实现改为委托共享 owner,一个问题一个答案。bundleDependencies不算 —— 它是名字数组,且其成员必须同时出现在dependencies里,不可能是唯一声明。workspace:*、link:../x、file:的值是包管理器的事;"foo": "npm:bar@1"下可导入的说明符是foo、manifest 的键也是foo,所以按键判定恰好对(import('bar')正确地读作未声明)。subpath 先剥掉再查(@objectstack/platform-objects/plugin→@objectstack/platform-objects),scoped 保留两段。node:内建、file:URL)绕过这道门 —— 那不是 manifest 能声明的东西,不该被拒。package.json:记为manifestMissing,报错直说「那里没有 package.json」,而不是误导性的「你没声明」。三个消费者,逐一验证
packages/clicreateHostImporter(hostRoot);D5 文案按分类分支;私有hostDeclaresDependency改为委托isDeclaredByHostpnpm --filter @objectstack/cli test→ 67 files / 589 tests passedpackages/verifycreateHostImporter(opts.hostRoot ?? cwd);bootStack的硬错误按分类分支pnpm --filter @objectstack/verify test→ 3 files / 12 tests passedpackages/qa/dogfoodreason/ 硬失败文案按分类给出可执行补救pnpm --filter @objectstack/dogfood test→ 81 passed / 1 skipped(475 passed / 3 skipped)dogfood 红线(#4700 刚修好的两道 enterprise 门)未被打回。 本仓里
@objectstack/organizations是 cloud-private、整个工作区任何位置都不存在,已核实:也就是说这里的 skip 在改动前后都成立,且不是本改动造成的 —— 没有任何夹具因新规则从「真能跑」退回 skip,因此没有需要放宽的地方。同时新增两条用例把红线正面钉住:一个 host 装了但没声明 时探测必须报 UNAVAILABLE,且硬失败文案给的是「声明它」而不是「安装它」(对一个明明已装好的包说「去装」是不可执行的建议)。
反向验证(贴真实输出)
把
node.ts的核心判断临时改回改前行为(if (declaration.declared)→if (true)),其余不动。1)单元层(
packages/types)最后一条最直白:旧代码成功导入了那个只存在于
NODE_PATHstore 里、host 从未声明的包({ hoisted: true })。2)真实进程层(
packages/clie2e,新增用例把 pnpm shim 原样复现)✓ Server is ready就是缺陷本身:改前,一个未声明任何依赖的 app 只因为环境里多了一个NODE_PATH,就把 walled posture 起起来了。(同批失败的第二条是部分回退的产物 —— 只回退了node.ts而serve.ts的文案分支还在,于是未声明的 app 走进了declared-unresolvable那条文案;全量回退时它是通过的。)改后:两条 e2e 文件 7/7 passed。
其它验证
turbo typecheck --filter=types --filter=cli --filter=verify --filter=dogfood→ 63 tasks successfuleslint --no-inline-config(仓库 lint 脚本的实际形态)覆盖全部改动文件 → exit 0packages/types/src/node-isolation.test.ts的node:内建白名单加入node:fs(读 manifest 需要),并写明理由 —— 这条 pin 存在就是为了防止 subpath 隔离变成空转。changeset
.changeset/host-declared-package-resolution.md,定级 minor(RC pre-mode 下契约收紧的先例:adr-0119-plugin-reachable-transactions.md;#4798 曾因错标 patch 被打回)。正文诚实写明了哪类部署会从假绿变红(请求了 walled posture 却没声明@objectstack/organizations、靠 hoisting 跑着的部署)与修法(在那个 app 的package.json里声明并安装),并给出createHostImporter签名变更的 FROM → TO。Generated by Claude Code